#pragma once 

#include <iostream>
#include <queue>
#include <pthread.h>

template <class T>
class ThreadPool{
    private:
        std::queue<T> q; //给线程池派发任务的地点, 临界资源
        pthread_mutex_t lock; // 这把锁用于控制多线程对线程池的互斥访问
        pthread_cond_t cond; // 条件变量
    private:
        ThreadPool()
        {
            // 完成对互斥锁和条件变量的初始化
            pthread_mutex_init(&lock, nullptr);
            pthread_cond_init(&cond, nullptr);
        }

        // 单例模式下要把赋值操作符和拷贝函数给禁了
        ThreadPool(const ThreadPool<T>&) = delete;
        ThreadPool<T>& operator = (const ThreadPool<T>&) = delete;
        static ThreadPool<T> *instance;
    public:
        // 获取单例
        static ThreadPool<T> *get_instance()
        {
            
            //static std::mutex lock;
            //lock.lock();
            static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
            // PTHREAD_MUTEX_INITIALIZER是一个宏，用于静态初始化互斥锁。使用这个宏可以在编译时将互斥锁初始化为默认状态，无需在运行时调用 pthread_mutex_init 函数进行初始化。
            
            // 如果单例还没有创建，我们就创建一个，如果已经创建好了，我们直接返回对应指针
            if(nullptr == instance){
                // 进入临界区之前上锁
                pthread_mutex_lock(&mtx);
                if(nullptr == instance){
                    // 进入之后需要再判断一下，这是为什么？
                        // 因为创建单例需要时间，如果当A线程进去正在创建单例的时候，B线程走到了第一个语句，此时单例还没创建好，B线程也能进来
                        // 如果我们不加这第二层if判断，那么进来的B线程，在等A线程把锁释放之后，也能进来再创建一个单例
                    instance = new ThreadPool<T>();
                    //instance->Init...
                }
                pthread_mutex_unlock(&mtx);
            }
            //lock.unlock();
            return instance;
        }

        // 访问任务队列之前，先上个锁
        void LockQueue()
        {
            pthread_mutex_lock(&lock);
        }
        
        // 结束对任务队列的访问时解锁
        void UnlockQueue()
        {
            pthread_mutex_unlock(&lock);
        }
        
        // 判断当前任务队列中是否有任务
        bool IsEmpty()
        {
            return q.size() == 0;
        }

        // 如果任务队列没任务，我们就要让当前线程阻塞等待
        void ThreadWait()
        {
            pthread_cond_wait(&cond, &lock);
            // 这句话的作用如下：
            // 1.解锁互斥锁：当调用 pthread_cond_wait 时，它会自动释放传入的互斥锁（mutex）。这一操作至关重要，因为若不释放互斥锁，其他线程就无法进入临界区来修改共享资源和触发条件变量，从而可能导致死锁。
            // 2.将当前线程放入条件变量 cond 的等待队列中，然后进入阻塞状态，等待其他线程通过 pthread_cond_signal 或 pthread_cond_broadcast 来唤醒它。
            // 3.当其他线程发出信号唤醒该线程后，pthread_cond_wait 会尝试重新获取之前释放的互斥锁。一旦成功获取互斥锁，函数就会返回，线程可以继续执行后续的代码。
        }
        
        // 等待新的任务被放进任务队列之后，再唤醒之前阻塞等待的线程，告诉它们来活了，拿任务去吧
        void ThreadWakeup()
        {
            pthread_cond_signal(&cond);
            // 唤醒在指定条件变量 cond 上等待的一个线程（如果有线程在等待的话）
                // 如果没有线程在该条件变量上等待，调用该函数不会产生任何效果。
        }

        // 取出任务队列中的一个任务，函数外创建一个out对象用来存储这个取出的任务
        void PopTask(T *out)
        {
            *out = q.front();
            q.pop();
        }

        //Routinue是类中的一个成员方法！包含了一个隐士参数this！ThreadPool*
        //实际上，这里是包含了两个参数的！
        static void *Routinue(void *args/*,ThreadPool *this*/)
        {
            pthread_detach(pthread_self()); //线程分离
            // 这个args实际上就是我们在调用pthread_create时传入的第四个参数，也就是ThreadPool类的this指针
            // this 指针是一个常量指针，指向调用该成员函数的对象，由于我们是单例模式，所以所有线程的this指针都会指向ThreadPool的单例
            ThreadPool *tp = (ThreadPool*)args;

            while(true){
                // 访问线程池之前先上锁
                tp->LockQueue();

                //1. 检测是否有任务
                // 如果任务队列为空，说明暂时没任务，那我们就等一等
                while(tp->IsEmpty()){
                    //thread 应该等待,等待有任务
                    tp->ThreadWait(); //我们线程当前是在临界区内等待的！我是持有锁的！！！
                }

                
                //2. 取任务
                // 走到这里说明到任务队列不为空，那我们就从队列中取一个任务，取到对象t中
                T t;
                tp->PopTask(&t);

                // 访问线程池结束，释放锁
                tp->UnlockQueue();

                //3. 处理任务, 拿到任务之后，处理任务的时候，需要在临界区内处理吗？不需要
                //在你的线程处理任务期间，其他线程是不是可以继续获取任务，处理任务
                t();
            }
        }
        void InitThreadPool(int num)
        {
            // 创建指定数量的线程
            for(auto i = 0; i < num; i++){
                pthread_t tid;
                pthread_create(&tid, nullptr, Routinue, this);
            }
        }

        // 向任务队列中添加一个任务（参数里面的in就是要添加的任务）
        void PushTask(const T &in)
        {
            //放任务
            LockQueue();
            q.push(in);
            ThreadWakeup(); // 唤醒一个正在等待任务的线程
            UnlockQueue();
        }

        ~ThreadPool()
        {
            pthread_mutex_destroy(&lock);
            pthread_cond_destroy(&cond);
        }
};

// 全局成员变量要在类外初始化
template<class T>
ThreadPool<T>* ThreadPool<T>::instance = nullptr;


